﻿namespace System.ServiceModel.DomainServices.Client
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Threading;
    using System.Windows.Data;

    public sealed class EntitySet<TEntity> : EntitySet, IEnumerable<TEntity>, IEnumerable, ICollectionViewFactory where TEntity: Entity
    {
        private EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityAdded;
        private EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityRemoved;

        public event EventHandler<EntityCollectionChangedEventArgs<TEntity>> EntityAdded
        {
            add
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityAdded = this.entityAdded;
                do
                {
                    handler2 = entityAdded;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Combine(handler2, value);
                    entityAdded = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityAdded, handler3, handler2);
                }
                while (entityAdded != handler2);
            }
            remove
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityAdded = this.entityAdded;
                do
                {
                    handler2 = entityAdded;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Remove(handler2, value);
                    entityAdded = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityAdded, handler3, handler2);
                }
                while (entityAdded != handler2);
            }
        }

        public event EventHandler<EntityCollectionChangedEventArgs<TEntity>> EntityRemoved
        {
            add
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityRemoved = this.entityRemoved;
                do
                {
                    handler2 = entityRemoved;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Combine(handler2, value);
                    entityRemoved = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityRemoved, handler3, handler2);
                }
                while (entityRemoved != handler2);
            }
            remove
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityRemoved = this.entityRemoved;
                do
                {
                    handler2 = entityRemoved;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Remove(handler2, value);
                    entityRemoved = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityRemoved, handler3, handler2);
                }
                while (entityRemoved != handler2);
            }
        }

        public EntitySet() : base(typeof(TEntity))
        {
        }

        public void Add(TEntity entity)
        {
            base.Add(entity);
        }

        public void Attach(TEntity entity)
        {
            base.Attach(entity);
        }

        protected override Entity CreateEntity()
        {
            if (typeof(TEntity).IsAbstract)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.Cannot_Create_Abstract_Entity, new object[] { typeof(TEntity) }));
            }
            return (TEntity) Activator.CreateInstance(typeof(TEntity));
        }

        protected override IList CreateList()
        {
            return new List<TEntity>();
        }

        public void Detach(TEntity entity)
        {
            base.Detach(entity);
        }

        public IEnumerator<TEntity> GetEnumerator()
        {
            return ((IList<TEntity>) base.List).GetEnumerator();
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedAction action, object affectedObject, int index)
        {
            if ((action == NotifyCollectionChangedAction.Add) && (this.entityAdded != null))
            {
                this.entityAdded(this, new EntityCollectionChangedEventArgs<TEntity>((TEntity) affectedObject));
            }
            else if ((action == NotifyCollectionChangedAction.Remove) && (this.entityRemoved != null))
            {
                this.entityRemoved(this, new EntityCollectionChangedEventArgs<TEntity>((TEntity) affectedObject));
            }
            else if ((action == NotifyCollectionChangedAction.Reset) && (this.entityRemoved != null))
            {
                foreach (TEntity local in ((IEnumerable) affectedObject).Cast<TEntity>())
                {
                    this.entityRemoved(this, new EntityCollectionChangedEventArgs<TEntity>(local));
                }
            }
            base.OnCollectionChanged(action, affectedObject, index);
        }

        public void Remove(TEntity entity)
        {
            base.Remove(entity);
        }

        IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        ICollectionView ICollectionViewFactory.CreateView()
        {
            CollectionViewSource source = new CollectionViewSource {
                Source = new ListCollectionViewProxy<TEntity, TEntity>((EntitySet<TEntity>) this)
            };
            return source.View;
        }

        private class ListCollectionViewProxy<T> : IList, ICollection, IEnumerable<T>, IEnumerable, INotifyCollectionChanged, ICollectionChangedListener where T: Entity
        {
            private readonly EntitySet<T> _source;
            private readonly object _syncRoot;
            private readonly WeakCollectionChangedListener _weakCollectionChangedLister;
            private NotifyCollectionChangedEventHandler collectionChanged;

            public event NotifyCollectionChangedEventHandler CollectionChanged
            {
                add
                {
                    NotifyCollectionChangedEventHandler handler2;
                    NotifyCollectionChangedEventHandler collectionChanged = this.collectionChanged;
                    do
                    {
                        handler2 = collectionChanged;
                        NotifyCollectionChangedEventHandler handler3 = (NotifyCollectionChangedEventHandler) Delegate.Combine(handler2, value);
                        collectionChanged = Interlocked.CompareExchange<NotifyCollectionChangedEventHandler>(ref this.collectionChanged, handler3, handler2);
                    }
                    while (collectionChanged != handler2);
                }
                remove
                {
                    NotifyCollectionChangedEventHandler handler2;
                    NotifyCollectionChangedEventHandler collectionChanged = this.collectionChanged;
                    do
                    {
                        handler2 = collectionChanged;
                        NotifyCollectionChangedEventHandler handler3 = (NotifyCollectionChangedEventHandler) Delegate.Remove(handler2, value);
                        collectionChanged = Interlocked.CompareExchange<NotifyCollectionChangedEventHandler>(ref this.collectionChanged, handler3, handler2);
                    }
                    while (collectionChanged != handler2);
                }
            }

            internal ListCollectionViewProxy(EntitySet<T> source)
            {
                this._syncRoot = new object();
                this._source = source;
                this._weakCollectionChangedLister = WeakCollectionChangedListener.CreateIfNecessary(this._source, this);
            }

            public int Add(object value)
            {
                T entity = value as T;
                if (entity == null)
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.MustBeAnEntity, new object[] { "value" }), "value");
                }
                this.Source.Add(entity);
                return this.IndexOf(entity);
            }

            public void Clear()
            {
                this.Source.Clear();
            }

            public bool Contains(object value)
            {
                return (this.IndexOf(value) >= 0);
            }

            public void CopyTo(Array array, int index)
            {
                this.Source.List.CopyTo(array, index);
            }

            public IEnumerator<T> GetEnumerator()
            {
                return this.Source.GetEnumerator();
            }

            public int IndexOf(object value)
            {
                return this.Source.List.IndexOf(value);
            }

            public void Insert(int index, object value)
            {
                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, new object[] { "Insert" }));
            }

            private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                NotifyCollectionChangedEventHandler collectionChanged = this.collectionChanged;
                if (collectionChanged != null)
                {
                    collectionChanged(this, e);
                }
            }

            private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                this.OnCollectionChanged(e);
            }

            public void Remove(object value)
            {
                T entity = (T) value;
                if (entity != null)
                {
                    this.Source.Remove(entity);
                }
            }

            public void RemoveAt(int index)
            {
                this.Remove(this[index]);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            void ICollectionChangedListener.OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                this.OnSourceCollectionChanged(sender, e);
            }

            public int Count
            {
                get
                {
                    return this.Source.Count;
                }
            }

            public bool IsFixedSize
            {
                get
                {
                    return (!this.Source.CanAdd && !this.Source.CanRemove);
                }
            }

            public bool IsReadOnly
            {
                get
                {
                    return (!this.Source.CanAdd && !this.Source.CanRemove);
                }
            }

            public bool IsSynchronized
            {
                get
                {
                    return false;
                }
            }

            public object this[int index]
            {
                get
                {
                    if ((index < 0) || (index >= this.Source.Count))
                    {
                        throw new ArgumentOutOfRangeException("index");
                    }
                    return this.Source.List[index];
                }
                set
                {
                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, new object[] { "Indexed setting" }));
                }
            }

            private EntitySet<T> Source
            {
                get
                {
                    return this._source;
                }
            }

            public object SyncRoot
            {
                get
                {
                    return this._syncRoot;
                }
            }
        }
    }
}

